# uncompyle6 version 3.2.3
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.8 |Anaconda custom (64-bit)| (default, Feb 21 2019, 18:30:04) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: site-packages\gevent\pywsgi.py
"""
A pure-Python, gevent-friendly WSGI server.

The server is provided in :class:`WSGIServer`, but most of the actual
WSGI work is handled by :class:`WSGIHandler` --- a new instance is
created for each request. The server can be customized to use
different subclasses of :class:`WSGIHandler`.

"""
import errno
from io import BytesIO
import string, sys, time, traceback
from datetime import datetime

try:
    from urllib import unquote
except ImportError:
    from urllib.parse import unquote

from gevent import socket
import gevent
from gevent.server import StreamServer
from gevent.hub import GreenletExit
from gevent._compat import PY3, reraise
from functools import partial

if PY3:
    unquote_latin1 = partial(unquote, encoding="latin-1")
else:
    unquote_latin1 = unquote
_no_undoc_members = True
__all__ = [
    "WSGIServer",
    "WSGIHandler",
    "LoggingLogAdapter",
    "Environ",
    "SecureEnviron",
    "WSGISecureEnviron",
]
MAX_REQUEST_LINE = 8192
_WEEKDAYNAME = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
_MONTHNAME = [
    None,
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec",
]
_HEX = string.hexdigits.encode("ascii")
_ERRORS = dict()
_INTERNAL_ERROR_STATUS = "500 Internal Server Error"
_INTERNAL_ERROR_BODY = b"Internal Server Error"
_INTERNAL_ERROR_HEADERS = [
    ("Content-Type", "text/plain"),
    ("Connection", "close"),
    ("Content-Length", str(len(_INTERNAL_ERROR_BODY))),
]
_ERRORS[500] = (_INTERNAL_ERROR_STATUS, _INTERNAL_ERROR_HEADERS, _INTERNAL_ERROR_BODY)
_BAD_REQUEST_STATUS = "400 Bad Request"
_BAD_REQUEST_BODY = ""
_BAD_REQUEST_HEADERS = [
    ("Content-Type", "text/plain"),
    ("Connection", "close"),
    ("Content-Length", str(len(_BAD_REQUEST_BODY))),
]
_ERRORS[400] = (_BAD_REQUEST_STATUS, _BAD_REQUEST_HEADERS, _BAD_REQUEST_BODY)
_REQUEST_TOO_LONG_RESPONSE = b"HTTP/1.1 414 Request URI Too Long\r\nConnection: close\r\nContent-length: 0\r\n\r\n"
_BAD_REQUEST_RESPONSE = (
    b"HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-length: 0\r\n\r\n"
)
_CONTINUE_RESPONSE = b"HTTP/1.1 100 Continue\r\n\r\n"


def format_date_time(timestamp):
    year, month, day, hh, mm, ss, wd, _y, _z = time.gmtime(timestamp)
    value = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
        _WEEKDAYNAME[wd],
        day,
        _MONTHNAME[month],
        year,
        hh,
        mm,
        ss,
    )
    if PY3:
        value = value.encode("latin-1")
    return value


class _InvalidClientInput(IOError):
    pass


class _InvalidClientRequest(ValueError):
    pass


class Input(object):
    __slots__ = (
        "rfile",
        "content_length",
        "socket",
        "position",
        "chunked_input",
        "chunk_length",
        "_chunked_input_error",
    )

    def __init__(self, rfile, content_length, socket=None, chunked_input=False):
        self.rfile = rfile
        self.content_length = content_length
        self.socket = socket
        self.position = 0
        self.chunked_input = chunked_input
        self.chunk_length = -1
        self._chunked_input_error = False

    def _discard(self):
        if self._chunked_input_error:
            return
        if self.socket is None:
            if self.position < (self.content_length or 0) or self.chunked_input:
                while 1:
                    d = self.read(16384)
                    if not d:
                        break

    def _send_100_continue(self):
        if self.socket is not None:
            self.socket.sendall(_CONTINUE_RESPONSE)
            self.socket = None

    def _do_read(self, length=None, use_readline=False):
        if use_readline:
            reader = self.rfile.readline
        else:
            reader = self.rfile.read
        content_length = self.content_length
        if content_length is None:
            return b""
        self._send_100_continue()
        left = content_length - self.position
        if length is None:
            length = left
        else:
            if length > left:
                length = left
        if not length:
            return b""
            try:
                read = reader(length)
            except OverflowError:
                if not use_readline:
                    raise
                read = b""
                while len(read) < length:
                    if not read.endswith(b"\n"):
                        read += reader(MAX_REQUEST_LINE)

            self.position += len(read)
            if len(read) < length:
                if use_readline and not read.endswith(b"\n") or not use_readline:
                    raise IOError(
                        "unexpected end of file while reading request at position %s"
                        % (self.position,)
                    )
            return read

    def __read_chunk_length(self, rfile):
        buf = BytesIO()
        while 1:
            char = rfile.read(1)
            if not char:
                self._chunked_input_error = True
                raise _InvalidClientInput("EOF before chunk end reached")
            if char == b"\r":
                break
            if char == b";":
                break
            if char not in _HEX:
                self._chunked_input_error = True
                raise _InvalidClientInput("Non-hex data", char)
            buf.write(char)
            if buf.tell() > 16:
                self._chunked_input_error = True
                raise _InvalidClientInput("Chunk-size too large.")

        if char == b";":
            i = 0
            while i < MAX_REQUEST_LINE:
                char = rfile.read(1)
                if char == b"\r":
                    break
                i += 1
            else:
                self._chunked_input_error = True
                raise _InvalidClientInput("Too large chunk extension")

        if char == b"\r":
            char = rfile.read(1)
            if char != b"\n":
                self._chunked_input_error = True
                raise _InvalidClientInput("Line didn't end in CRLF")
            return int(buf.getvalue(), 16)

    def _chunked_read(self, length=None, use_readline=False):
        rfile = self.rfile
        self._send_100_continue()
        if length == 0:
            return b""
        else:
            if length is not None:
                if length < 0:
                    length = None
            if use_readline:
                reader = self.rfile.readline
            else:
                reader = self.rfile.read
            response = []
            while self.chunk_length != 0:
                maxreadlen = self.chunk_length - self.position
                if length is not None:
                    if length < maxreadlen:
                        maxreadlen = length
                if maxreadlen > 0:
                    data = reader(maxreadlen)
                    if not data:
                        self.chunk_length = 0
                        self._chunked_input_error = True
                        raise IOError(
                            "unexpected end of file while parsing chunked data"
                        )
                    datalen = len(data)
                    response.append(data)
                    self.position += datalen
                    if self.chunk_length == self.position:
                        rfile.readline()
                    if length is not None:
                        length -= datalen
                        if length == 0:
                            break
                    if use_readline:
                        if data[-1] == 10:
                            break
                else:
                    self.chunk_length = self._Input__read_chunk_length(rfile)
                    self.position = 0
                    if self.chunk_length == 0:
                        rfile.readline()

            return (b"").join(response)

    def read(self, length=None):
        if self.chunked_input:
            return self._chunked_read(length)
        else:
            return self._do_read(length)

    def readline(self, size=None):
        if self.chunked_input:
            return self._chunked_read(size, True)
        else:
            return self._do_read(size, use_readline=True)

    def readlines(self, hint=None):
        return list(self)

    def __iter__(self):
        return self

    def next(self):
        line = self.readline()
        if not line:
            raise StopIteration
        return line

    __next__ = next


try:
    import mimetools

    headers_factory = mimetools.Message
except ImportError:
    from http import client

    class OldMessage(client.HTTPMessage):
        def __init__(self, **kwargs):
            super(client.HTTPMessage, self).__init__(**kwargs)
            self.status = ""

        def getheader(self, name, default=None):
            return self.get(name, default)

        @property
        def headers(self):
            for key, value in self._headers:
                yield "%s: %s\r\n" % (key, value)

        @property
        def typeheader(self):
            return self.get("content-type")

    def headers_factory(fp, *args):
        try:
            ret = client.parse_headers(fp, _class=OldMessage)
        except client.LineTooLong:
            ret = OldMessage()
            ret.status = "Line too long"

        return ret


class WSGIHandler(object):
    """
    Handles HTTP requests from a socket, creates the WSGI environment, and
    interacts with the WSGI application.
    
    This is the default value of :attr:`WSGIServer.handler_class`.
    This class may be subclassed carefully, and that class set on a
    :class:`WSGIServer` instance through a keyword argument at
    construction time.
    
    Instances are constructed with the same arguments as passed to the
    server's :meth:`WSGIServer.handle` method followed by the server
    itself. The application and environment are obtained from the server.
    
    """

    protocol_version = "HTTP/1.1"
    if PY3:

        def MessageClass(self, *args):
            return headers_factory(*args)

    else:
        MessageClass = headers_factory
    status = None
    _orig_status = None
    response_headers = None
    code = None
    provided_date = None
    provided_content_length = None
    close_connection = False
    time_start = 0
    time_finish = 0
    headers_sent = False
    response_use_chunked = False
    environ = None
    application = None
    requestline = None
    response_length = 0
    result = None
    wsgi_input = None
    content_length = 0
    headers = headers_factory(BytesIO())
    request_version = None
    command = None
    path = None

    def __init__(self, sock, address, server, rfile=None):
        self.socket = sock
        self.client_address = address
        self.server = server
        if rfile is None:
            self.rfile = sock.makefile("rb", -1)
        else:
            self.rfile = rfile

    def handle(self):
        """
        The main request handling method, called by the server.
        
        This method runs a request handling loop, calling
        :meth:`handle_one_request` until all requests on the
        connection have been handled (that is, it implements
        keep-alive).
        """
        try:
            while self.socket is not None:
                self.time_start = time.time()
                self.time_finish = 0
                result = self.handle_one_request()
                if result is None:
                    break
                if result is True:
                    continue
                self.status, response_body = result
                self.socket.sendall(response_body)
                if self.time_finish == 0:
                    self.time_finish = time.time()
                self.log_request()
                break

        finally:
            if self.socket is not None:
                _sock = getattr(self.socket, "_sock", None)
                try:
                    if _sock:
                        try:
                            _sock.recv(16384)
                        finally:
                            _sock.close()

                    self.socket.close()
                except socket.error:
                    pass

                self.__dict__.pop("socket", None)
                self.__dict__.pop("rfile", None)

    def _check_http_version(self):
        version_str = self.request_version
        if not version_str.startswith("HTTP/"):
            return False
        else:
            version = tuple((int(x) for x in version_str[5:].split(".")))
            if version[1] < 0 or version < (0, 9) or version >= (2, 0):
                return False
            return True

    def read_request(self, raw_requestline):
        """
        Parse the incoming request.
        
        Parses various headers into ``self.headers`` using
        :attr:`MessageClass`. Other attributes that are set upon a successful
        return of this method include ``self.content_length`` and ``self.close_connection``.
        
        :param str raw_requestline: A native :class:`str` representing
           the request line. A processed version of this will be stored
           into ``self.requestline``.
        
        :raises ValueError: If the request is invalid. This error will
           not be logged as a traceback (because it's a client issue, not a server problem).
        :return: A boolean value indicating whether the request was successfully parsed.
           This method should either return a true value or have raised a ValueError
           with details about the parsing error.
        
        .. versionchanged:: 1.1b6
           Raise the previously documented :exc:`ValueError` in more cases instead of returning a
           false value; this allows subclasses more opportunity to customize behaviour.
        """
        self.requestline = raw_requestline.rstrip()
        words = self.requestline.split()
        if len(words) == 3:
            self.command, self.path, self.request_version = words
            if not self._check_http_version():
                raise _InvalidClientRequest("Invalid http version: %r", raw_requestline)
            else:
                if len(words) == 2:
                    self.command, self.path = words
                    if self.command != "GET":
                        raise _InvalidClientRequest(
                            "Expected GET method: %r", raw_requestline
                        )
                    self.request_version = "HTTP/0.9"
                else:
                    raise _InvalidClientRequest(
                        "Invalid HTTP method: %r", raw_requestline
                    )
                self.headers = self.MessageClass(self.rfile, 0)
                if self.headers.status:
                    raise _InvalidClientRequest(
                        "Invalid headers status: %r", self.headers.status
                    )
        if self.headers.get("transfer-encoding", "").lower() == "chunked":
            try:
                del self.headers["content-length"]
            except KeyError:
                pass

            content_length = self.headers.get("content-length")
            if content_length is not None:
                content_length = int(content_length)
                if content_length < 0:
                    raise _InvalidClientRequest(
                        "Invalid Content-Length: %r", content_length
                    )
                if content_length:
                    if self.command in ("HEAD",):
                        raise _InvalidClientRequest("Unexpected Content-Length")
            self.content_length = content_length
            if self.request_version == "HTTP/1.1":
                conntype = self.headers.get("Connection", "").lower()
                self.close_connection = conntype == "close"
            else:
                self.close_connection = True
            return True

    def log_error(self, msg, *args):
        try:
            message = msg % args
        except Exception:
            traceback.print_exc()
            message = "%r %r" % (msg, args)

        try:
            message = "%s: %s" % (self.socket, message)
        except Exception:
            pass

        try:
            self.server.error_log.write(message + "\n")
        except Exception:
            traceback.print_exc()

    def read_requestline(self):
        """
        Read and return the HTTP request line.
        
        Under both Python 2 and 3, this should return the native
        ``str`` type; under Python 3, this probably means the bytes read
        from the network need to be decoded (using the ISO-8859-1 charset, aka
        latin-1).
        """
        line = self.rfile.readline(MAX_REQUEST_LINE)
        if PY3:
            line = line.decode("latin-1")
        return line

    def handle_one_request(self):
        """
        Handles one HTTP request using ``self.socket`` and ``self.rfile``.
        
        Each invocation of this method will do several things, including (but not limited to):
        
        - Read the request line using :meth:`read_requestline`;
        - Read the rest of the request, including headers, with :meth:`read_request`;
        - Construct a new WSGI environment in ``self.environ`` using :meth:`get_environ`;
        - Store the application in ``self.application``, retrieving it from the server;
        - Handle the remainder of the request, including invoking the application,
          with :meth:`handle_one_response`
        
        There are several possible return values to indicate the state
        of the client connection:
        
        - ``None``
            The client connection is already closed or should
            be closed because the WSGI application or client set the
            ``Connection: close`` header. The request handling
            loop should terminate and perform cleanup steps.
        - (status, body)
            An HTTP status and body tuple. The request was in error,
            as detailed by the status and body. The request handling
            loop should terminate, close the connection, and perform
            cleanup steps. Note that the ``body`` is the complete contents
            to send to the client, including all headers and the initial
            status line.
        - ``True``
            The literal ``True`` value. The request was successfully handled
            and the response sent to the client by :meth:`handle_one_response`.
            The connection remains open to process more requests and the connection
            handling loop should call this method again. This is the typical return
            value.
        
        .. seealso:: :meth:`handle`
        
        .. versionchanged:: 1.1b6
           Funnel exceptions having to do with invalid HTTP requests through
           :meth:`_handle_client_error` to allow subclasses to customize. Note that
           this is experimental and may change in the future.
        """
        if self.rfile.closed:
            return
            try:
                self.requestline = self.read_requestline()
                if PY3:
                    if isinstance(self.requestline, bytes):
                        self.requestline = self.requestline.decode("latin-1")
            except socket.error:
                return

            if not self.requestline:
                return
            self.response_length = 0
            if len(self.requestline) >= MAX_REQUEST_LINE:
                return ("414", _REQUEST_TOO_LONG_RESPONSE)
                try:
                    if not self.read_request(self.requestline):
                        return ("400", _BAD_REQUEST_RESPONSE)
                except Exception as ex:
                    return self._handle_client_error(ex)

                self.environ = self.get_environ()
                self.application = self.server.application
                self.handle_one_response()
                if self.close_connection:
                    return
                if self.rfile.closed:
                    return
                return True

    def finalize_headers(self):
        if self.provided_date is None:
            self.response_headers.append((b"Date", format_date_time(time.time())))
        if self.code not in (304, 204):
            if self.provided_content_length is None:
                if hasattr(self.result, "__len__"):
                    total_len = sum((len(chunk) for chunk in self.result))
                    total_len_str = str(total_len)
                    if PY3:
                        total_len_str = total_len_str.encode("latin-1")
                    self.response_headers.append((b"Content-Length", total_len_str))
                else:
                    if self.request_version != "HTTP/1.0":
                        self.response_use_chunked = True
                        self.response_headers.append((b"Transfer-Encoding", b"chunked"))

    def _sendall(self, data):
        try:
            self.socket.sendall(data)
        except socket.error as ex:
            self.status = "socket error: %s" % ex
            if self.code > 0:
                self.code = -self.code
            raise

        self.response_length += len(data)

    def _write(self, data):
        if not data:
            return
        if self.response_use_chunked:
            header = ("%x\r\n" % len(data)).encode("ascii")
            self._sendall(header)
            self._sendall(data)
            self._sendall(b"\r\n")
        else:
            self._sendall(data)

    def write(self, data):
        if self.code in (304, 204):
            if data:
                raise AssertionError("The %s response must have no body" % self.code)
        if self.headers_sent:
            self._write(data)
        else:
            if not self.status:
                raise AssertionError("The application did not call start_response()")
            self._write_with_headers(data)

    def _write_with_headers(self, data):
        towrite = bytearray()
        self.headers_sent = True
        self.finalize_headers()
        towrite.extend(b"HTTP/1.1 ")
        towrite.extend(self.status)
        towrite.extend(b"\r\n")
        for header, value in self.response_headers:
            towrite.extend(header)
            towrite.extend(b": ")
            towrite.extend(value)
            towrite.extend(b"\r\n")

        towrite.extend(b"\r\n")
        self._sendall(towrite)
        self._write(data)

    def start_response(self, status, headers, exc_info=None):
        """
         .. versionchanged:: 1.2a1
            Avoid HTTP header injection by raising a :exc:`ValueError`
            if *status* or any *header* name or value contains a carriage
            return or newline.
         .. versionchanged:: 1.1b5
            Pro-actively handle checking the encoding of the status line
            and headers during this method. On Python 2, avoid some
            extra encodings.
        """
        if exc_info:
            try:
                if self.headers_sent:
                    reraise(*exc_info)
            finally:
                exc_info = None

        response_headers = []
        header = None
        value = None
        try:
            for header, value in headers:
                if not isinstance(header, str):
                    raise UnicodeError(
                        "The header must be a native string", header, value
                    )
                if not isinstance(value, str):
                    raise UnicodeError(
                        "The value must be a native string", header, value
                    )
                if "\r" in header or "\n" in header:
                    raise ValueError(
                        "carriage return or newline in header name", header
                    )
                if "\r" in value or "\n" in value:
                    raise ValueError(
                        "carriage return or newline in header value", value
                    )
                response_headers.append(
                    (PY3headerheader.encode("latin-1"), PY3valuevalue.encode("latin-1"))
                )

        except UnicodeEncodeError:
            raise UnicodeError("Non-latin1 header", repr(header), repr(value))

        if not isinstance(status, str):
            raise UnicodeError("The status string must be a native string")
        if "\r" in status or "\n" in status:
            raise ValueError("carriage return or newline in status", status)
        code = int(status.split(" ", 1)[0])
        self.status = PY3statusstatus.encode("latin-1")
        self._orig_status = status
        self.response_headers = response_headers
        self.code = code
        provided_connection = None
        self.provided_date = None
        self.provided_content_length = None
        for header, value in headers:
            header = header.lower()
            if header == "connection":
                provided_connection = value
            else:
                if header == "date":
                    self.provided_date = value
                else:
                    if header == "content-length":
                        self.provided_content_length = value

        if self.request_version == "HTTP/1.0":
            if provided_connection is None:
                response_headers.append((b"Connection", b"close"))
                self.close_connection = True
            if provided_connection == "close":
                self.close_connection = True
        if self.code in (304, 204):
            if self.provided_content_length is not None:
                if self.provided_content_length != "0":
                    msg = (
                        "Invalid Content-Length for %s response: %r (must be absent or zero)"
                        % (self.code, self.provided_content_length)
                    )
                    if PY3:
                        msg = msg.encode("latin-1")
                    raise AssertionError(msg)
        return self.write

    def log_request(self):
        self.server.log.write(self.format_request() + "\n")

    def format_request(self):
        now = datetime.now().replace(microsecond=0)
        length = self.response_length or "-"
        if self.time_finish:
            delta = "%.6f" % (self.time_finish - self.time_start)
        else:
            delta = "-"
        client_address = (
            self.client_address[0]
            if isinstance(self.client_address, tuple)
            else self.client_address
        )
        return '%s - - [%s] "%s" %s %s %s' % (
            client_address or "-",
            now,
            self.requestline or "",
            (self._orig_status or self.status or "000").split()[0],
            length,
            delta,
        )

    def process_result(self):
        for data in self.result:
            if data:
                self.write(data)

        if self.status:
            if not self.headers_sent:
                self.write(b"")
        if self.response_use_chunked:
            self.socket.sendall(b"0\r\n\r\n")
            self.response_length += 5

    def run_application(self):
        if not self.result is None:
            raise AssertionError
        try:
            self.result = self.application(self.environ, self.start_response)
            self.process_result()
        finally:
            close = getattr(self.result, "close", None)
            try:
                if close is not None:
                    close()
            finally:
                close = None
                self.result = None

    def handle_one_response(self):
        self.time_start = time.time()
        self.status = None
        self.headers_sent = False
        self.result = None
        self.response_use_chunked = False
        self.response_length = 0
        try:
            try:
                self.run_application()
            finally:
                try:
                    self.wsgi_input._discard()
                except (socket.error, IOError):
                    pass

        except _InvalidClientInput:
            self._send_error_response_if_possible(400)
        except socket.error as ex:
            if ex.args[0] in (errno.EPIPE, errno.ECONNRESET):
                if not PY3:
                    sys.exc_clear()
                self.close_connection = True
            else:
                self.handle_error(*sys.exc_info())
        except:
            self.handle_error(*sys.exc_info())
        finally:
            self.time_finish = time.time()
            self.log_request()

    def _send_error_response_if_possible(self, error_code):
        if self.response_length:
            self.close_connection = True
        else:
            status, headers, body = _ERRORS[error_code]
        try:
            self.start_response(status, headers[:])
            self.write(body)
        except socket.error:
            if not PY3:
                sys.exc_clear()
            self.close_connection = True

    def _log_error(self, t, v, tb):
        if not issubclass(t, GreenletExit):
            context = self.environ
            if not isinstance(context, self.server.secure_environ_class):
                context = self.server.secure_environ_class(context)
            self.server.loop.handle_error(context, t, v, tb)

    def handle_error(self, t, v, tb):
        self._log_error(t, v, tb)
        del tb
        self._send_error_response_if_possible(500)

    def _handle_client_error(self, ex):
        if not isinstance(ex, ValueError):
            traceback.print_exc()
        if isinstance(ex, _InvalidClientRequest):
            self.log_error(*ex.args)
        else:
            self.log_error("Invalid request: %s", str(ex) or ex.__class__.__name__)
        return ("400", _BAD_REQUEST_RESPONSE)

    def _headers(self):
        key = None
        value = None
        IGNORED_KEYS = (None, "CONTENT_TYPE", "CONTENT_LENGTH")
        for header in self.headers.headers:
            if key is not None:
                if header[:1] in " \t":
                    value += header
                    continue
                if key not in IGNORED_KEYS:
                    yield ("HTTP_" + key, value.strip())
                key, value = header.split(":", 1)
                if "_" in key:
                    key = None
                else:
                    key = key.replace("-", "_").upper()

        if key not in IGNORED_KEYS:
            yield ("HTTP_" + key, value.strip())

    def get_environ(self):
        """
        Construct and return a new WSGI environment dictionary for a specific request.
        
        This should begin with asking the server for the base environment
        using :meth:`WSGIServer.get_environ`, and then proceed to add the
        request specific values.
        
        By the time this method is invoked the request line and request shall have
        been parsed and ``self.headers`` shall be populated.
        """
        env = self.server.get_environ()
        env["REQUEST_METHOD"] = self.command
        env["SCRIPT_NAME"] = ""
        if "?" in self.path:
            path, query = self.path.split("?", 1)
        else:
            path, query = self.path, ""
        env["PATH_INFO"] = unquote_latin1(path)
        env["QUERY_STRING"] = query
        if self.headers.typeheader is not None:
            env["CONTENT_TYPE"] = self.headers.typeheader
        length = self.headers.getheader("content-length")
        if length:
            env["CONTENT_LENGTH"] = length
        env["SERVER_PROTOCOL"] = self.request_version
        client_address = self.client_address
        if isinstance(client_address, tuple):
            env["REMOTE_ADDR"] = str(client_address[0])
            env["REMOTE_PORT"] = str(client_address[1])
        for key, value in self._headers():
            if key in env:
                if "COOKIE" in key:
                    env[key] += "; " + value
                else:
                    env[key] += "," + value
            else:
                env[key] = value

        if env.get("HTTP_EXPECT") == "100-continue":
            sock = self.socket
        else:
            sock = None
        chunked = env.get("HTTP_TRANSFER_ENCODING", "").lower() == "chunked"
        self.wsgi_input = Input(
            self.rfile, self.content_length, socket=sock, chunked_input=chunked
        )
        env["wsgi.input"] = self.wsgi_input
        return env


class _NoopLog(object):
    def write(self, *args, **kwargs):
        pass

    def flush(self):
        pass

    def writelines(self, *args, **kwargs):
        pass


class LoggingLogAdapter(object):
    """
    An adapter for :class:`logging.Logger` instances
    to let them be used with :class:`WSGIServer`.
    
    .. warning:: Unless the entire process is monkey-patched at a very
        early part of the lifecycle (before logging is configured),
        loggers are likely to not be gevent-cooperative. For example,
        the socket and syslog handlers use the socket module in a way
        that can block, and most handlers acquire threading locks.
    
    .. warning:: It *may* be possible for the logging functions to be
       called in the :class:`gevent.Hub` greenlet. Code running in the
       hub greenlet cannot use any gevent blocking functions without triggering
       a ``LoopExit``.
    
    .. versionadded:: 1.1a3
    
    .. versionchanged:: 1.1b6
       Attributes not present on this object are proxied to the underlying
       logger instance. This permits using custom :class:`~logging.Logger`
       subclasses (or indeed, even duck-typed objects).
    
    .. versionchanged:: 1.1
       Strip trailing newline characters on the message passed to :meth:`write`
       because log handlers will usually add one themselves.
    """

    __slots__ = ("_logger", "_level")

    def __init__(self, logger, level=20):
        """
        Write information to the *logger* at the given *level* (default to INFO).
        """
        self._logger = logger
        self._level = level

    def write(self, msg):
        if msg:
            if msg.endswith("\n"):
                msg = msg[:-1]
        self._logger.log(self._level, msg)

    def flush(self):
        """No-op; required to be a file-like object"""
        pass

    def writelines(self, lines):
        for line in lines:
            self.write(line)

    def __getattr__(self, name):
        return getattr(self._logger, name)

    def __setattr__(self, name, value):
        if name not in LoggingLogAdapter.__slots__:
            setattr(self._logger, name, value)
        else:
            object.__setattr__(self, name, value)

    def __delattr__(self, name):
        delattr(self._logger, name)


class Environ(dict):
    """
    A base class that can be used for WSGI environment objects.
    
    Provisional API.
    
    .. versionadded:: 1.2a1
    """

    __slots__ = ()

    def copy(self):
        return self.__class__(self)

    if not hasattr(dict, "iteritems"):

        def iteritems(self):
            return self.items()

    def __reduce_ex__(self, proto):
        return (dict, (), None, None, iter(self.iteritems()))


class SecureEnviron(Environ):
    """
    An environment that does not print its keys and values
    by default.
    
    Provisional API.
    
    This is intended to keep potentially sensitive information like
    HTTP authorization and cookies from being inadvertently printed
    or logged.
    
    For debugging, each instance can have its *secure_repr* attribute
    set to ``False``, which will cause it to print like a normal dict.
    
    When *secure_repr* is ``True`` (the default), then the value of
    the *whitelist_keys* attribute is consulted; if this value is
    true-ish, it should be a container (something that responds to
    ``in``) of key names (typically a list or set). Keys and values in
    this dictionary that are in *whitelist_keys* will then be printed,
    while all other values will be masked. These values may be
    customized on the class by setting the *default_secure_repr* and
    *default_whitelist_keys*, respectively::
    
        >>> environ = SecureEnviron(key='value')
        >>> environ # doctest: +ELLIPSIS
        <pywsgi.SecureEnviron dict (keys: 1) at ...
    
    If we whitelist the key, it gets printed::
    
        >>> environ.whitelist_keys = {'key'}
        >>> environ
        {'key': 'value'}
    
    A non-whitelisted key (*only*, to avoid doctest issues) is masked::
    
        >>> environ['secure'] = 'secret'; del environ['key']
        >>> environ
        {'secure': '<MASKED>'}
    
    We can turn it off entirely for the instance::
    
        >>> environ.secure_repr = False
        >>> environ
        {'secure': 'secret'}
    
    We can also customize it at the class level (here we use a new
    class to be explicit and to avoid polluting the true default
    values; we would set this class to be the ``environ_class`` of the
    server)::
    
        >>> class MyEnviron(SecureEnviron):
        ...    default_whitelist_keys = ('key',)
        ...
        >>> environ = MyEnviron({'key': 'value'})
        >>> environ
        {'key': 'value'}
    
    .. versionadded:: 1.2a1
    """

    default_secure_repr = True
    default_whitelist_keys = ()
    default_print_masked_keys = True
    __slots__ = ("secure_repr", "whitelist_keys", "print_masked_keys")

    def __getattr__(self, name):
        if name in SecureEnviron.__slots__:
            return getattr(type(self), "default_" + name)
        raise AttributeError(name)

    def __repr__(self):
        if self.secure_repr:
            whitelist = self.whitelist_keys
            print_masked = self.print_masked_keys
            if whitelist:
                safe = {
                    k: (self[k] if k in whitelist else "<MASKED>")
                    for k in self
                    if k in whitelist or print_masked
                }
                safe_repr = repr(safe)
                if not print_masked:
                    if len(safe) != len(self):
                        safe_repr = safe_repr[:-1] + ", (hidden keys: %d)}" % (
                            len(self) - len(safe)
                        )
                return safe_repr
            return "<pywsgi.SecureEnviron dict (keys: %d) at %s>" % (
                len(self),
                id(self),
            )
        else:
            return Environ.__repr__(self)

    __str__ = __repr__


class WSGISecureEnviron(SecureEnviron):
    """
    Specializes the default list of whitelisted keys to a few
    common WSGI variables.
    
    Example::
    
       >>> environ = WSGISecureEnviron(REMOTE_ADDR='::1', HTTP_AUTHORIZATION='secret')
       >>> environ
       {'REMOTE_ADDR': '::1', (hidden keys: 1)}
       >>> import pprint
       >>> pprint.pprint(environ)
       {'REMOTE_ADDR': '::1', (hidden keys: 1)}
       >>> print(pprint.pformat(environ))
       {'REMOTE_ADDR': '::1', (hidden keys: 1)}
    """

    default_whitelist_keys = ("REMOTE_ADDR", "REMOTE_PORT", "HTTP_HOST")
    default_print_masked_keys = False


class WSGIServer(StreamServer):
    """
    A WSGI server based on :class:`StreamServer` that supports HTTPS.
    
    
    :keyword log: If given, an object with a ``write`` method to which
        request (access) logs will be written. If not given, defaults
        to :obj:`sys.stderr`. You may pass ``None`` to disable request
        logging. You may use a wrapper, around e.g., :mod:`logging`,
        to support objects that don't implement a ``write`` method.
        (If you pass a :class:`~logging.Logger` instance, or in
        general something that provides a ``log`` method but not a
        ``write`` method, such a wrapper will automatically be created
        and it will be logged to at the :data:`~logging.INFO` level.)
    
    :keyword error_log: If given, a file-like object with ``write``,
        ``writelines`` and ``flush`` methods to which error logs will
        be written. If not given, defaults to :obj:`sys.stderr`. You
        may pass ``None`` to disable error logging (not recommended).
        You may use a wrapper, around e.g., :mod:`logging`, to support
        objects that don't implement the proper methods. This
        parameter will become the value for ``wsgi.errors`` in the
        WSGI environment (if not already set). (As with *log*,
        wrappers for :class:`~logging.Logger` instances and the like
        will be created automatically and logged to at the :data:`~logging.ERROR`
        level.)
    
    .. seealso::
    
        :class:`LoggingLogAdapter`
            See important warnings before attempting to use :mod:`logging`.
    
    .. versionchanged:: 1.1a3
        Added the ``error_log`` parameter, and set ``wsgi.errors`` in the WSGI
        environment to this value.
    .. versionchanged:: 1.1a3
        Add support for passing :class:`logging.Logger` objects to the ``log`` and
        ``error_log`` arguments.
    """

    handler_class = WSGIHandler
    log = None
    error_log = None
    environ_class = dict
    secure_environ_class = WSGISecureEnviron
    base_env = {
        "GATEWAY_INTERFACE": "CGI/1.1",
        "SERVER_SOFTWARE": "gevent/%d.%d Python/%d.%d" % gevent.version_info[:2]
        + sys.version_info[:2],
        "SCRIPT_NAME": "",
        "wsgi.version": (1, 0),
        "wsgi.multithread": False,
        "wsgi.multiprocess": False,
        "wsgi.run_once": False,
    }

    def __init__(
        self,
        listener,
        application=None,
        backlog=None,
        spawn="default",
        log="default",
        error_log="default",
        handler_class=None,
        environ=None,
        **ssl_args
    ):
        StreamServer.__init__(backlog=backlog, spawn=spawn, **ssl_args)
        if application is not None:
            self.application = application
        if handler_class is not None:
            self.handler_class = handler_class

        def _make_log(l, level=20):
            if l == "default":
                return sys.stderr
            elif l is None:
                return _NoopLog()
            else:
                if not hasattr(l, "write"):
                    if hasattr(l, "log"):
                        return LoggingLogAdapter(l, level)
                return l

        self.log = _make_log(log)
        self.error_log = _make_log(error_log, 40)
        self.set_environ(environ)
        self.set_max_accept()

    def set_environ(self, environ=None):
        if environ is not None:
            self.environ = environ
        environ_update = getattr(self, "environ", None)
        self.environ = self.environ_class(self.base_env)
        if self.ssl_enabled:
            self.environ["wsgi.url_scheme"] = "https"
        else:
            self.environ["wsgi.url_scheme"] = "http"
        if environ_update is not None:
            self.environ.update(environ_update)
        if self.environ.get("wsgi.errors") is None:
            self.environ["wsgi.errors"] = self.error_log

    def set_max_accept(self):
        if self.environ.get("wsgi.multiprocess"):
            self.max_accept = 1

    def get_environ(self):
        return self.environ_class(self.environ)

    def init_socket(self):
        StreamServer.init_socket(self)
        self.update_environ()

    def update_environ(self):
        """
        Called before the first request is handled to fill in WSGI environment values.
        
        This includes getting the correct server name and port.
        """
        address = self.address
        if isinstance(address, tuple):
            if "SERVER_NAME" not in self.environ:
                try:
                    name = socket.getfqdn(address[0])
                except socket.error:
                    name = str(address[0])

                if PY3:
                    if not isinstance(name, str):
                        name = name.decode("ascii")
                self.environ["SERVER_NAME"] = name
            self.environ.setdefault("SERVER_PORT", str(address[1]))
        else:
            self.environ.setdefault("SERVER_NAME", "")
            self.environ.setdefault("SERVER_PORT", "")

    def handle(self, sock, address):
        """
        Create an instance of :attr:`handler_class` to handle the request.
        
        This method blocks until the handler returns.
        """
        handler = self.handler_class(sock, address, self)
        handler.handle()


def _main():
    from gevent import monkey

    monkey.patch_all()
    import argparse, importlib

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "app", help="dotted name of WSGI app callable [module:callable]"
    )
    parser.add_argument("-b", "--bind", help="The socket to bind", default=":8080")
    args = parser.parse_args()
    module_name, app_name = args.app.split(":")
    module = importlib.import_module(module_name)
    app = getattr(module, app_name)
    bind = args.bind
    server = WSGIServer(bind, app)
    server.serve_forever()


if __name__ == "__main__":
    _main()
